/**
 * @file wrapper.hpp
 * @author 逆流 (1171267147@qq.com)
 * @brief opencl接口封装器，自动管理相关资源
 * @version 0.1
 * @date 2025-11-08
 *
 * @copyright Copyright (c) 2025
 *
 */
#ifndef WRAPPER_HPP
#define WRAPPER_HPP

#include <cstddef>
#ifdef __APPLE__
#include <OpenCL/opencl.h>
#else
#include <CL/opencl.h>
#endif
#include <iostream>

#if __cplusplus >= 201703L
template <typename... Args>
void ReleaseCLMemObject(Args &&...args)
{
    // 折叠表达式，遍历args对其调用前面的三元表达式
    ((args ? clReleaseMemObject(args) : CL_SUCCESS), ...);
}
#else
template <typename... Args>
void ReleaseCLMemObject(Args... args)
{
    // 定义一个lambda函数，并将args作为initializer_list作为入参调用该lambda
    [](std::initializer_list<cl_mem> mem_objects)
    {
        for (cl_mem mem : mem_objects)
        {
            if (mem) clReleaseMemObject(mem);
        }
    }({args...});
}
#endif

const char *GetErrorString(cl_int error)
{
#define DEAL_CASE(x) \
    case x:          \
        return #x

    switch (error)
    {
        DEAL_CASE(CL_SUCCESS);
        DEAL_CASE(CL_DEVICE_NOT_FOUND);
        DEAL_CASE(CL_DEVICE_NOT_AVAILABLE);
        DEAL_CASE(CL_MEM_OBJECT_ALLOCATION_FAILURE);
        DEAL_CASE(CL_COMPILER_NOT_AVAILABLE);
        DEAL_CASE(CL_BUILD_PROGRAM_FAILURE);
        DEAL_CASE(CL_INVALID_PROGRAM);
        default:
            return "Unknown OpenCL error";
    }
#undef DEAL_CASE
}

#define CHECK_ERROR(error)                               \
    if (error != CL_SUCCESS)                             \
    {                                                    \
        std::cerr << GetErrorString(error) << std::endl; \
    }

#define CHECK_ERROR_WITH_GOTO_EXIT(error)                \
    if (error != CL_SUCCESS)                             \
    {                                                    \
        std::cerr << GetErrorString(error) << std::endl; \
        goto EXIT;                                       \
    }

/**
 * @brief 管理opencl资源
 *
 */
class CLWrapper {
public:
    CLWrapper() = default;
    ~CLWrapper() { DeInit(); }

    bool Init();
    void DeInit();

    cl_platform_id   GetPlatformID() const { return platform_; }
    cl_device_id     GetDeviceID() const { return device_; }
    cl_context       GetContext() const { return context_; }
    cl_command_queue GetQueue() const { return queue_; }

    cl_program CreateProgram(const char *source,
                             size_t      length,
                             const char *options = nullptr);

    cl_kernel CreateKernel(const char *kernel_name);

private:  // 静态私有成员
    static constexpr size_t kMaxLogLength = 8096;

private:
    cl_platform_id   platform_{nullptr};
    cl_device_id     device_{nullptr};
    cl_context       context_{nullptr};
    cl_command_queue queue_{nullptr};
    cl_program       program_{nullptr};
    cl_kernel        kernel_{nullptr};
};

bool CLWrapper::Init()
{
    bool flag = false;

    // 获取平台ID
    cl_int err = clGetPlatformIDs(1, &platform_, NULL);
    CHECK_ERROR_WITH_GOTO_EXIT(err);

    // 获取设备ID
    err = clGetDeviceIDs(platform_, CL_DEVICE_TYPE_ALL, 1, &device_, NULL);
    CHECK_ERROR_WITH_GOTO_EXIT(err);

    // 创建上下文
    context_ = clCreateContext(NULL, 1, &device_, NULL, NULL, &err);
    CHECK_ERROR_WITH_GOTO_EXIT(err);

    // 创建命令队列
    queue_ =
        clCreateCommandQueueWithProperties(context_, device_, nullptr, &err);
    CHECK_ERROR_WITH_GOTO_EXIT(err);

    flag = true;

EXIT:
    return flag;
}

void CLWrapper::DeInit()
{
    if (kernel_)
    {
        clReleaseKernel(kernel_);
        kernel_ = nullptr;
    }

    if (program_)
    {
        clReleaseProgram(program_);
        program_ = nullptr;
    }

    if (queue_)
    {
        clReleaseCommandQueue(queue_);
        queue_ = nullptr;
    }

    if (context_)
    {
        clReleaseContext(context_);
        context_ = nullptr;
    }

    device_   = nullptr;
    platform_ = nullptr;
}

cl_program CLWrapper::CreateProgram(const char *source,
                                    size_t      length,
                                    const char *options)
{
    cl_int err = CL_SUCCESS;

    if (program_)
    {
        clReleaseProgram(program_);
        program_ = nullptr;
    }

    program_ = clCreateProgramWithSource(context_, 1, &source, &length, &err);
    CHECK_ERROR_WITH_GOTO_EXIT(err);

    err = clBuildProgram(program_, 1, &device_, options, nullptr, nullptr);
    if (err != CL_SUCCESS)
    {
        std::cerr << GetErrorString(err) << std::endl;
        size_t log_length;
        clGetProgramBuildInfo(
            program_, device_, CL_PROGRAM_BUILD_LOG, 0, nullptr, &log_length);

        char log[kMaxLogLength]{0};
        log_length =
            log_length >= kMaxLogLength ? kMaxLogLength - 1 : log_length;
        clGetProgramBuildInfo(
            program_, device_, CL_PROGRAM_BUILD_LOG, log_length, log, nullptr);
        std::cout << log << std::endl;

        program_ = nullptr;
    }

EXIT:
    return program_;
}

cl_kernel CLWrapper::CreateKernel(const char *kernel_name)
{
    cl_int err = CL_SUCCESS;

    kernel_ = clCreateKernel(program_, kernel_name, &err);
    CHECK_ERROR(err);

    return kernel_;
}

#endif